#if HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#if HAVE_CURSES_H
#include <curses.h>
#elif HAVE_NCURSES_CURSES_H
#include <ncurses/curses.h>
#endif /* HAVE_CURSES_H */

#if HAVE_STDLIB_H 
#include <stdlib.h>
#endif  /* HAVE_STDLIB_H */

#if HAVE_STRING_H
#include <string.h>
#endif /* HAVE_STRING_H */

#if HAVE_MATH_H
#include <math.h>
#endif /* HAVE_MATH_H */

#include "threadsdlg.h"
#include "cgdb.h"
#include "highlight.h"
#include "kui_term.h"
#include "highlight_groups.h"


struct threads_buffer {
    int length;                     /* Number of threads in program */
    char **threads;                   /* Array containing thread */
    char *cur_line;                 /* cur line may have unique color */
    int    max_width;                  /* Width of longest line in thread */

    int               sel_line;     /* Current line selected in thread dialog */
    int               sel_col;      /* Current column selected in thread dialog */

    int               sel_col_rbeg; /* Current beg column matched in regex */
    int               sel_col_rend; /* Current end column matched in regex */
    int               sel_rline;    /* Current line used by regex */
};

struct threadsdlg{
    struct threads_buffer    *buf;     /* All of the widget's data ( threads ) */
    WINDOW *win;                    /* Curses window */
};

static char regex_line[MAX_LINE];       /* The regex the user enters */
static int regex_line_pos;              /* The index into the current regex */
static int regex_search;                /* Currently searching text ? */
static int regex_direction;             /* Direction to search */

void threadsdlg_move(struct threadsdlg *scr, 
        int pos_r, int pos_c, int height, int width)
{
    delwin(scr->win);
    scr->win = newwin(height, width, pos_r, pos_c);
    wclear(scr->win);
} 


/* count: Count the occurrences of a character c in a string s.
 * ------
 *
 *   s:  String to search
 *   c:  Character to search for
 *
 * Return Value:  Number of occurrences of c in s.
 */
static int count(const char *s, char c)
{
    int rv = 0;
    char *x = strchr(s, c);

    while (x){
        rv++;
        x = strchr(x+1, c);
    }

    return rv;
}

/* parse: Translates special characters in a string.  (i.e. backspace, tab...)
 * ------
 *
 *   buf:  The string to parse
 *
 * Return Value:  A newly allocated copy of buf, with modifications made.
 */
static char *parse(const char *orig, const char *buf)
{
    const int tab_size = 8;
    int length = strlen(orig) + strlen(buf) + (tab_size-1) * count(buf, '\t');
    char *rv = (char *) malloc(length + 1);
    int i, j;

    /* Zero out the string */
    memset(rv, 0, length+1);
    strcpy(rv, orig);
    i = strlen(orig);

    /* Expand special characters */
    for (j = 0; j < strlen(buf); j++){
        switch (buf[j]){
            /* Backspace/Delete -> Erase last character */
            case 8:
            case 127:
                if (i > 0)
                    i--;
                break;
                /* Tab -> Translating to spaces */
            case '\t':
                do
                    rv[i++] = ' ';
                while (i % tab_size != 0);
                break;
                /* Carriage return -> Move back to the beginning of the line */
            case '\r':
                i = 0;
                break;
                /* Default case -> Only keep printable characters */
            default:
                if (isprint((int)buf[j])){
                    rv[i] = buf[j];
                    i++;
                }
                break;
        }
    }

    //  c_stdlg->buf->sel_line = i;
    /* Remove trailing space from the line */
    for (j = strlen(rv)-1; j > i && isspace((int)rv[j]); j--);
    rv[j+1] = 0;

    return realloc(rv, strlen(rv)+1);
}



/* print_in_middle: Prints the message 'string' centered at line in win 
 * ----------------
 *
 *  win:    Curses window
 *  line:   The line to print the message at
 *  width:  The width of the window
 *  string: The message to print
 */
static void print_in_middle(WINDOW *win, int line, int width, char *string) {  
    int  x, y;
    int j;
    int length = strlen(string);

    getyx(win, y, x);

    x = (int)((width-length)/2);

    wmove(win, line, 0);
    for (j = 0; j < x; j++)
        waddch(win, ' ');

    mvwprintw(win, line, x, "%s", string);

    for (j = x + length; j < width; j++)
        waddch(win, ' ');
}

struct threadsdlg *threadsdlg_new(int pos_r, int pos_c, int height, int width)
{

    struct threadsdlg *thrd;

    /* Allocate a new structure */
    if ((thrd = malloc(sizeof(struct threadsdlg))) == NULL)
        return NULL;

    /* Initialize the structure */
    thrd->win = newwin(height, width, pos_r, pos_c);
    keypad(thrd->win, TRUE);

    /* Initialize the buffer */
    if ( ( thrd->buf = malloc(sizeof(struct threads_buffer))) == NULL)
        return NULL;

    thrd->buf->length             = 0;
    thrd->buf->threads              = NULL;
    thrd->buf->cur_line           = NULL;
    thrd->buf->max_width          = 0;
    thrd->buf->sel_line           = 0;
    thrd->buf->sel_col            = 0;
    thrd->buf->sel_col_rbeg       = 0;
    thrd->buf->sel_col_rend       = 0;
    thrd->buf->sel_rline          = 0;

    return thrd;
}
void threadsdlg_free(struct threadsdlg *thrd)
{
    threadsdlg_clear(thrd);
    delwin(thrd->win);
    free(thrd->buf);
    free(thrd);
}

int threadsdlg_add_thread(struct threadsdlg *thrd, const char *thread_choice)
{
    int distance;               /* Distance to next new line character */
    int length;                 /* Length of the current line */ 
    char *x;                    /* Pointer to next new line character */
    int index, i;
    int equal = 1;      /* Not set to 0, because 0 *is* equal */

    if ( thread_choice == NULL || *thread_choice == '\0' || (strlen(thread_choice) == 1 && *thread_choice == '\n') )
        return -1;

    /* Find next newline in the string */
    x        = strchr(thread_choice, '\n');
    if (thrd->buf->length == 0)
    {
        thrd->buf->threads = malloc(sizeof(char*));
        thrd->buf->threads[0] = malloc(sizeof(char));
        *thrd->buf->threads[0] = '\0';
        thrd->buf->length++;
    }

    distance = x ? x-thread_choice : strlen(thread_choice);

    /* Append to the last line in the buffer */
    if (distance > 0){
        char *temp = thrd->buf->threads[thrd->buf->length-1];
        char *buf2 = malloc(distance + 1);
        strncpy(buf2, thread_choice, distance);
        buf2[distance] = 0;
        thrd->buf->threads[thrd->buf->length-1] = parse(temp, buf2);
        if(thrd->buf->threads[thrd->buf->length-1][0] == '*')
            thrd->buf->sel_line = thrd->buf->length-1;
        free(temp);
        free(buf2);
    }

    /* Create additional lines if buf contains newlines */
    while (x != NULL){
        char *newbuf;
        thread_choice    = x + 1;
        x        = strchr(thread_choice, '\n');
        distance = x ? x-thread_choice : strlen(thread_choice);

        /* Create a new buffer that stops at the next newline */
        newbuf = malloc(distance+1);
        memset(newbuf, 0, distance+1);
        strncpy(newbuf, thread_choice, distance);

        /* Expand the buffer */
        thrd->buf->length++;
        thrd->buf->threads = realloc(thrd->buf->threads, sizeof(char *)*thrd->buf->length);


        /* Add the new line */
        thrd->buf->threads[thrd->buf->length-1] = parse("", newbuf);
        if(thrd->buf->threads[thrd->buf->length-1][0] == '*')
            thrd->buf->sel_line = thrd->buf->length-1;
        free(newbuf);
    }

    return 0;
}
void threadsdlg_clear(struct threadsdlg *thrd)
{
    int i;

    for ( i = 0; i < thrd->buf->length; i++ )
        free(thrd->buf->threads[i]);

    free(thrd->buf->threads);
    thrd->buf->threads       = NULL;

    free(thrd->buf->cur_line);
    thrd->buf->cur_line    = NULL;

    thrd->buf->max_width   = 0;
    thrd->buf->length      = 0;
    thrd->buf->sel_line           = 0;
    thrd->buf->sel_col            = 0;
    thrd->buf->sel_col_rbeg       = 0;
    thrd->buf->sel_col_rend       = 0;
    thrd->buf->sel_rline          = 0;

}

static void threadsdlg_vscroll(struct threadsdlg *thrd, int offset) {
    if (thrd->buf){
        thrd->buf->sel_line += offset;
        if (thrd->buf->sel_line < 0)
            thrd->buf->sel_line = 0;
        /* The display message and status bar takes a line */
        if (thrd->buf->sel_line >= thrd->buf->length - 1)
            thrd->buf->sel_line = thrd->buf->length - 2;
    }
}

static void threadsdlg_hscroll(struct threadsdlg *thrd, int offset) {
    int lwidth;
    int max_width;

    if (thrd->buf){
        lwidth    = (int) log10(thrd->buf->length)+1;
        max_width = thrd->buf->max_width - COLS + lwidth + 6;

        thrd->buf->sel_col += offset;
        if (thrd->buf->sel_col > max_width)
            thrd->buf->sel_col = max_width;
        if (thrd->buf->sel_col < 0)
            thrd->buf->sel_col = 0;
    }
}

    static void threadsdlg_search_regex_init(struct threadsdlg *thrd) {
        if ( thrd == NULL || thrd->buf == NULL)
            return;

        /* Start from beggining of line if not at same line */
        if ( thrd->buf->sel_rline != thrd->buf->sel_line ) {
            thrd->buf->sel_col_rend = 0;
            thrd->buf->sel_col_rbeg = 0;
        }

        /* Start searching at the beggining of the selected line */
        thrd->buf->sel_rline    = thrd->buf->sel_line;
    }

static int threadsdlg_search_regex(struct threadsdlg *thrd, const char *regex,
        int opt, int direction, int icase) {
    if ( thrd == NULL    || thrd->buf == NULL || 
            regex == NULL || strlen(regex) == 0)
        return -1;

    return hl_regex(regex, 
            (const char **)thrd->buf->threads,
            (const char **)thrd->buf->threads,
            thrd->buf->length,
            &thrd->buf->cur_line, &thrd->buf->sel_line,
            &thrd->buf->sel_rline, &thrd->buf->sel_col_rbeg,
            &thrd->buf->sel_col_rend, opt, direction, icase);
}

void threadsdlg_display_message(struct threadsdlg *thrd, char *message) {
    int height, width, i;
    int attr;

    if (hl_groups_get_attr (hl_groups_instance, HLG_STATUS_BAR, &attr) == -1)
        return;

    getmaxyx(thrd->win, height, width);

    /* Print white background */
    wattron(thrd->win, attr);

    for ( i = 0; i < width; i++)
        mvwprintw(thrd->win, height - 1, i, " ");

    mvwprintw(thrd->win, height - 1, 0, "%s" , message);
    wattroff(thrd->win, attr);
    wrefresh(thrd->win);
}

/* capture_regex: Captures a regular expression from the user.
 * ---------------
 *  Side Effect: 
 *
 *  regex_line: The regex the user has entered.
 *  regex_line_pos: The next available index into regex_line.
 *
 * Return Value: 0 if user gave a regex, otherwise 1.
 */
static int capture_regex(struct threadsdlg *thrd) {
    int c;
    extern struct kui_manager *kui_ctx;

    /* Initialize the function for finding a regex and tell user */
    regex_search = 1;
    regex_line_pos = 0;
    regex_line[regex_line_pos] = '\0';
    threadsdlg_display(thrd, true);

    do {
        c = kui_manager_getkey_blocking (kui_ctx);

        if ( regex_line_pos == (MAX_LINE - 1) && !(c == CGDB_KEY_ESC || c == 8 || c == 127 ))
            continue;

        /* Quit the search if the user hit escape */
        if ( c == CGDB_KEY_ESC ) {
            regex_line_pos = 0;
            regex_line[regex_line_pos] = '\0';
            regex_search = 0;
            threadsdlg_search_regex(thrd, regex_line, 2, regex_direction, 1);
            threadsdlg_display(thrd, true);
            return 1;
        }

        /* If the user hit enter, then a successful regex has been recieved */
        if ( c == '\r' || c == '\n' || c == CGDB_KEY_CTRL_M) {
            regex_line[regex_line_pos] = '\0';
            break;
        }

        /* If the user hit backspace or delete remove a char */
        if ( CGDB_BACKSPACE_KEY(c) ) {
            if (regex_line_pos > 0)
                --regex_line_pos;

            regex_line[regex_line_pos] = '\0';
            threadsdlg_search_regex(thrd, regex_line, 1, regex_direction, 1);
            threadsdlg_display(thrd, true);
            continue;
        }

        /* Add a char, search and draw */
        regex_line[regex_line_pos++] = c;
        regex_line[regex_line_pos] = '\0';
        threadsdlg_search_regex(thrd, regex_line, 1, regex_direction, 1);
        threadsdlg_display(thrd, true);
    } while (1);

    /* Finished */
    regex_search = 0;
    threadsdlg_search_regex(thrd, regex_line, 2, regex_direction, 1);
    threadsdlg_display(thrd, true);
    return 0;
}


int threadsdlg_recv_char(struct threadsdlg *thrd, int key, char *thread) {
    int height, width;

    /* Initialize size variables */
    getmaxyx(thrd->win, height, width);

    //    threadsdlg_display(thrd, true); 

    switch ( key ) {
        case 'q':
            return -1;
            /* Vertical scrolling */
        case CGDB_KEY_DOWN:
        case 'j':
            threadsdlg_vscroll(thrd, 1);
            break;
        case CGDB_KEY_NPAGE:
        case CGDB_KEY_CTRL_F:	/* VI-style page down */
            threadsdlg_vscroll(thrd, height - 1);
            break;
        case CGDB_KEY_UP:
        case 'k':
            threadsdlg_vscroll(thrd, -1);
            break;
        case CGDB_KEY_PPAGE:
        case CGDB_KEY_CTRL_B:	/* VI-style page up */
            threadsdlg_vscroll(thrd, -(height - 1));
            break;
            /* Horizontal scrolling */
        case CGDB_KEY_RIGHT:
        case 'l':
            return -2;
            break;
        case CGDB_KEY_LEFT:
        case 'h':
            break;
        case '/':
        case '?':
            regex_direction = ('/' == key);

            /* Capturing regular expressions */
            threadsdlg_search_regex_init(thrd);
            capture_regex(thrd);
            break;
        case 'n':
            threadsdlg_search_regex(thrd, regex_line, 2, regex_direction, 1); 
            break;
        case 'N':
            threadsdlg_search_regex(thrd, regex_line, 2, !regex_direction, 1);
            break;
            /* User selected a thread */
        case '\n':
        case '\r':
        case CGDB_KEY_CTRL_M:
            if(thrd->buf->length != 0)
            {
                strcpy(thread, thrd->buf->threads[thrd->buf->sel_line]);
                return 1; 
            }
            else
                return 0;
        default:
            break;
    }

    threadsdlg_display(thrd, true); 

    return 0;
}
int threadsdlg_display( struct threadsdlg *thrd, bool is_in_focus)
{
    char fmt[5];
    int width, height;
    int lwidth;
    int thread;
    int i;
    int attr;
    static char *label = "Select a thread.";

    curs_set(0);

    /* Check that a thread is loaded */
    if (thrd == NULL || thrd->buf == NULL || thrd->buf->threads == NULL){
        wrefresh(thrd->win);
        return 0;
    }

    /* Initialize variables */
    getmaxyx(thrd->win, height, width);

    /* The status bar and display line 
     * Fake the display function to think the height is 2 lines less */
    height-=3;

    /* Set starting line number (center source thread if it's small enough)*/ 
    if (thrd->buf->length < height)
        thread = 0;
    else{
        thread = thrd->buf->sel_line - height / 2;
        if (thread > thrd->buf->length - height)
            thread = thrd->buf->length - height;
        if (thread < 0)
            thread = 0;
    }

    /* Print 'height' lines of the thread, starting at 'thread' */
    lwidth = (int) log10(thrd->buf->length)+1;
    sprintf(fmt, "%s", "%d");

    print_in_middle(thrd->win, 0, width, label);
    wmove(thrd->win, 1, 0);

    for (i = 0; i < width; i++)
        waddch(thrd->win, ' ');

    for (i = 2; i < height + 1; i++, thread++){
        wmove(thrd->win, i, 0);
        if (has_colors()){
            /* Outside of threadname, just finish drawing the vertical thread */
            if (thread < 0 || thread >= thrd->buf->length-1){
                int j;
                for (j = 1; j < lwidth; j++)
                    waddch(thrd->win, ' ');
                waddch(thrd->win, '~');
                wattron(thrd->win, A_BOLD);
                waddch(thrd->win, VERT_LINE);
                wattroff(thrd->win, A_BOLD);
                for (j = 2+lwidth; j < width; j++)
                    waddch(thrd->win, ' ');
            }
            /* Mark the current thread with an arrow */
            else if (thread == thrd->buf->sel_line){
                wattron(thrd->win, A_BOLD);
                wattroff(thrd->win, A_BOLD);
                if (hl_groups_get_attr (hl_groups_instance, HLG_ARROW, &attr) == -1)
                    return -1;
                if(is_in_focus)
                {
                    wattron(thrd->win, attr);
                    waddch(thrd->win, '-');
                    waddch(thrd->win, '>');
                }
                else
                {
                    wattron(thrd->win, A_BOLD);
                    waddch(thrd->win, ' ');
                    waddch(thrd->win, '=');
                    wattroff(thrd->win, A_BOLD);
                }
                if ( thrd->buf->cur_line != NULL )
                    hl_wprintw(thrd->win, thrd->buf->cur_line, width-lwidth-2, thrd->buf->sel_col);
                else
                    hl_wprintw(thrd->win, thrd->buf->threads[thread], width-lwidth-2, thrd->buf->sel_col);


                if(is_in_focus)
                {
                    wattroff(thrd->win, attr);
                }
            }
            /* Ordinary thread */
            else{
                wattron(thrd->win, A_BOLD);
                waddch(thrd->win, VERT_LINE);
                wattroff(thrd->win, A_BOLD);
                waddch(thrd->win, ' ');

                /* No special thread information */
                if ( thread == thrd->buf->sel_line && thrd->buf->cur_line != NULL )
                    hl_wprintw(thrd->win, thrd->buf->cur_line, width-lwidth-2, thrd->buf->sel_col);
                else
                    hl_wprintw(thrd->win, thrd->buf->threads[thread], width-lwidth-2, thrd->buf->sel_col);
            }
        }
        else{
            wprintw(thrd->win, "%s\n", thrd->buf->threads[thread]);
        }
    }

    /* Add the 2 lines back in so the status bar can be drawn */
    height += 2;

    /* Update status bar */
    wmove(thrd->win, height , 0);

    /* Print white background */
    if (hl_groups_get_attr (hl_groups_instance, HLG_STATUS_BAR, &attr) == -1)
        return -1;
    wattron(thrd->win, attr);

    for ( i = 0; i < width; i++)
        mvwprintw(thrd->win, height - 1, i, " ");

    if ( regex_search && regex_direction)
        mvwprintw(thrd->win, height - 1, 0, "Search:%s" , regex_line);
    else if ( regex_search )
        mvwprintw(thrd->win, height - 1, 0, "RSearch:%s" , regex_line);

    wattroff(thrd->win, attr);

    wmove(thrd->win, height - (thread - thrd->buf->sel_line) - 1, lwidth+2);
    wrefresh(thrd->win);

    return 0;
}


